package com.pjb.springbootjjwt.interceptor;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.pjb.springbootjjwt.annotation.PassToken;
import com.pjb.springbootjjwt.annotation.UserLoginToken;
import com.pjb.springbootjjwt.entity.User;
import com.pjb.springbootjjwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;


/**
 * @author jinbin
 * @date 2018-07-08 20:41
 */
public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        // 从 http 请求头中取出 token
        String token = httpServletRequest.getHeader("token");
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //检查是否有@passtoken注释，有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        //检查有没有@UserLoginToken 需要校验token的注解
        if (method.isAnnotationPresent(UserLoginToken.class)) {
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if (userLoginToken.required()) {
                // 执行认证
                if (token == null) {
                    throw new RuntimeException("无token，请重新登录");
                }
                // 获取 token 中的 user id
                String userId;
                try {
                    //JWT.decode(token) --jwt解码
                    //JWT.decode(token).getAudience()  --目标受众
                    //JWT.decode(token).getAudience().get(0); --token中的userId
                    userId = JWT.decode(token).getAudience().get(0);
                } catch (JWTDecodeException j) {
                    System.out.println("获取 token 中的 user id 失败！！！");
                    //返回401错误 401指未授权
                    throw new RuntimeException("401");
                }
                User user = userService.findUserById(userId);
                if (user == null) {
                    throw new RuntimeException("用户不存在，请重新登录");
                }


                /**
                 * 声明加密的算法:通常直接使用 HMAC SHA256
                 *
                 * 加密算法是单向函数散列算法，常见的有MD5、SHA、HAMC。
                 * MD5(message-digest algorithm 5) （信息-摘要算法）缩写，广泛用于加密和解密技术，常用于文件校验。校验？不管文件多大，经过MD5后都能生成唯一的MD5值
                 * SHA (Secure Hash Algorithm，安全散列算法），数字签名等密码学应用中重要的工具，安全性高于MD5
                 * HMAC (Hash Message Authentication Code)，散列消息鉴别码，基于密钥的Hash算法的认证协议。用公开函数和密钥产生一个固定长度的值作为认证标识，用这个标识鉴别消息的完整性。常用于接口签名验证
                 *
                 */
                // 验证 token
                //Algorithm.HMAC256(user.getPassword()) -- 经过HMAC256加密的密码
                //JWTVerifier类持有verify方法，以断言给定令牌不仅具有正确的JWT格式，而且其签名匹配
                JWTVerifier jwtVerifier =
                        JWT.require(Algorithm.HMAC256(user.getPassword()))
                        //签名由谁生成
                        .withIssuer("Server")
                        .build();
                try {
                    //通过JWTVerifier 的verify方法获取 token中的信息
                    //jwtVerifier.verify会校验token是否正确、有效
                    DecodedJWT decodedJWT = jwtVerifier.verify(token);

                    System.out.println("token生成时间:"+decodedJWT.getIssuedAt());
                    System.out.println("token有效期:"+decodedJWT.getExpiresAt());

                    String subject = decodedJWT.getSubject();
                    List<String> audience = decodedJWT.getAudience();
                    Map<String, Claim> claims = decodedJWT.getClaims();
                    System.out.println("获取-签名主题subject " + subject);
                    System.out.println("获取-签名观众list " + audience.toString());
//                    audience.forEach(System.out::println);
                    System.out.println("获取-签名载荷map " + claims.toString());

                } catch (JWTVerificationException e) {
                    System.out.println("JWTVerifier签名校验失败");
                    //返回401错误 401指未授权
                    throw new RuntimeException("401");
                }
                return true;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }


    /**
     * 实现一个拦截器就需要实现HandlerInterceptor接口
     *
     * HandlerInterceptor接口主要定义了三个方法
     * 1.boolean preHandle ()：
     * 预处理回调方法,实现处理器的预处理，第三个参数为响应的处理器,自定义Controller,返回值为true表示继续流程（如调用下一个拦截器或处理器）或者接着执行
     * postHandle()和afterCompletion()；false表示流程中断，不会继续调用其他的拦截器或处理器，中断执行。
     *
     * 2.void postHandle()：
     * 后处理回调方法，实现处理器的后处理（DispatcherServlet进行视图返回渲染之前进行调用），此时我们可以通过modelAndView（模型和视图对象）对模型数据进行处理或对视图进行处理，modelAndView也可能为null。
     *
     * 3.void afterCompletion():
     * 整个请求处理完毕回调方法,该方法也是需要当前对应的Interceptor的preHandle()的返回值为true时才会执行，也就是在DispatcherServlet渲染了对应的视图之后执行。用于进行资源清理。整个请求处理完毕回调方法。如性能监控中我们可以在此记录结束时间并输出消耗时间，还可以进行一些资源清理，类似于try-catch-finally中的finally，但仅调用处理器执行链中
     */
}
